/**
 * 
 */
package org.statewalker.parser.wiki.grammar;

import org.statewalker.validator.AbstractValidator;
import org.statewalker.validator.IParseKey;

/**
 * @author kotelnikov
 */
public class WikiBlockGrammar extends WikiInlineGrammar {

    public final IParseKey S_BLOCKQUOTE = newKey("blockquote");

    public final IParseKey S_CONTENT1 = newKey("content1");

    public final IParseKey S_CONTENT2 = newKey("content2");

    public final IParseKey S_CONTENT3 = newKey("content3");

    public final IParseKey S_CONTENT4 = newKey("content4");

    public final IParseKey S_CONTENT5 = newKey("content5");

    public final IParseKey S_CONTENT6 = newKey("content6");

    public final IParseKey[] S_CONTENTS = {
        S_CONTENT1,
        S_CONTENT2,
        S_CONTENT3,
        S_CONTENT4,
        S_CONTENT5,
        S_CONTENT6 };

    public final IParseKey S_DD = newKey("dd");

    public final IParseKey S_DEFAULT_CONTENT = newKey("defaultContent");

    public final IParseKey S_DEFAULT_SECTION = newKey("defaultSection");

    public final IParseKey S_DL = newKey("dl");

    public final IParseKey S_DOCUMENT = newKey("document");

    public final IParseKey S_DT = newKey("dt");

    public final IParseKey S_H1 = newKey("h1");

    public final IParseKey S_H2 = newKey("h2");

    public final IParseKey S_H3 = newKey("h3");

    public final IParseKey S_H4 = newKey("h4");

    public final IParseKey S_H5 = newKey("h5");

    public final IParseKey S_H6 = newKey("h6");

    public final IParseKey[] S_HEADS = { S_H1, S_H2, S_H3, S_H4, S_H5, S_H6 };

    public final IParseKey S_HR = newKey("hr");

    public final IParseKey S_LI = newKey("li");

    public final IParseKey S_OL = newKey("ol");

    public final IParseKey S_P = newKey("p");

    public final IParseKey S_PRE = newKey("pre");

    public final IParseKey S_SECTION1 = newKey("section1");

    public final IParseKey S_SECTION2 = newKey("section2");

    public final IParseKey S_SECTION3 = newKey("section3");

    public final IParseKey S_SECTION4 = newKey("section4");

    public final IParseKey S_SECTION5 = newKey("section5");

    public final IParseKey S_SECTION6 = newKey("section6");

    public final IParseKey[] S_SECTIONS = {
        S_SECTION1,
        S_SECTION2,
        S_SECTION3,
        S_SECTION4,
        S_SECTION5,
        S_SECTION6 };

    public final IParseKey S_TABLE = newKey("table");

    public final IParseKey S_TD = newKey("td");

    public final IParseKey S_TH = newKey("th");

    public final IParseKey S_TR = newKey("tr");

    public final IParseKey S_UL = newKey("ul");

    public WikiBlockGrammar(AbstractValidator<?, ?> validator) {
        super(validator);
    }

    private void addBlockElements(IParseKey key) {
        addInlineExtended(key);
        defineTransition(key, S_SECTION1, getFINAL());
        defineTransition(key, S_DEFAULT_SECTION, getFINAL());
        addDocument(key);
    }

    private void addContentElements(IParseKey key) {
        addDocument(key);
        defineSubstates(key, S_P);
        defineSubstates(key, S_PRE);
        defineSubstates(key, S_BLOCKQUOTE);
        defineSubstates(key, S_TABLE);
        defineSubstates(key, S_HR);
        addLists(key);
    }

    private void addDocument(IParseKey key) {
        defineSubstates(key, S_DOCUMENT);
    }

    private void addInlineExtended(IParseKey key) {
        defineSubstates(key, S_STYLE);
    }

    // http://www.w3.org/TR/xhtml1/dtds.html#dtdentry_xhtml1-strict.dtd_lists
    // <!ENTITY % lists "ul | ol | dl">
    private void addLists(IParseKey key) {
        defineSubstates(key, S_UL, S_OL, S_DL);
    }

    private IParseKey getBlockParent() {
        return S_DEFAULT_CONTENT;
    }

    @Override
    protected IParseKey getStyleContainer() {
        return S_P;
    }

    @Override
    public void init() {
        super.init();
        initDOCUMENT();

        // Sections
        initDEFAULT_SECTION();
        initSECTIONS();

        // Blocks
        initBLOCKQUOTE();
        initDL();
        initDD();
        initDT();
        initHR();
        initLI();
        initOL();
        initP();
        initPRE();
        initTABLE();
        initTD();
        initTH();
        initTR();
        initUL();

    }

    protected void initBLOCKQUOTE() {
        defineParent(S_BLOCKQUOTE, getBlockParent());
        addBlockElements(S_BLOCKQUOTE);
    }

    // http://www.w3.org/TR/xhtml1/dtds.html#dtdentry_xhtml1-strict.dtd_dd
    // <!ELEMENT dd %Flow;>
    protected void initDD() {
        defineParent(S_DD, S_DL);
        addBlockElements(S_DD);
        addLists(S_DD);
        if (useExtendedRules()) {
            defineTransition(S_DD, S_DD, getFINAL());
            defineTransition(S_DD, S_DT, getFINAL());
        }
    }

    protected void initDEFAULT_SECTION() {
        defineParent(S_DEFAULT_SECTION, S_DOCUMENT);
        defineParent(S_DEFAULT_CONTENT, S_DEFAULT_SECTION);
        defineTransition(
            S_DEFAULT_SECTION,
            getINITIAL(),
            getANY(),
            S_DEFAULT_CONTENT);
        addContentElements(S_DEFAULT_CONTENT);
        // Closes the default section if there is a defined section
        for (int j = 0; j < S_SECTIONS.length; j++) {
            defineTransition(S_DEFAULT_CONTENT, S_SECTIONS[j], getFINAL());
        }
    }

    // http://www.w3.org/TR/xhtml1/dtds.html#dtdentry_xhtml1-strict.dtd_dl
    // <!ELEMENT dl (dt|dd)+>
    protected void initDL() {
        defineParent(S_DL, getBlockParent());
        defineSubstates(S_DL, S_DD, S_DT);
        // By default (if it is not a "dd" or "dt" tag) the list should
        // create a "dd" element.
        defineTransition(S_DL, getINITIAL(), getANY(), S_DD);
    }

    // http://www.w3.org/TR/xhtml1/dtds.html#dtdentry_xhtml1-strict.dtd_html
    // <!ELEMENT html (head, body)>
    protected void initDOCUMENT() {
        defineParent(S_DOCUMENT, getROOT());
        defineTransition(S_DOCUMENT, getINITIAL(), getANY(), S_DEFAULT_SECTION);
        defineTransition(S_DOCUMENT, S_SECTION1, S_SECTION1);
        defineTransition(S_DOCUMENT, getANY(), getERROR());
    }

    // http://www.w3.org/TR/xhtml1/dtds.html#dtdentry_xhtml1-strict.dtd_dt
    // <!ELEMENT dt %Inline;>
    protected void initDT() {
        defineParent(S_DT, S_DL);
        addInlineExtended(S_DT);
    }

    protected void initHR() {
        defineParent(S_HR, getBlockParent());
    }

    // http://www.w3.org/TR/xhtml1/dtds.html#dtdentry_xhtml1-strict.dtd_li
    // <!ELEMENT li %Flow;>
    protected void initLI() {
        defineParent(S_LI, S_UL);
        addBlockElements(S_LI);
        addLists(S_LI);
        if (useExtendedRules()) {
            defineTransition(S_LI, S_LI, getFINAL());
        }
    }

    private void initList(IParseKey key) {
        defineParent(key, getBlockParent());
        defineSubstates(key, S_LI);
        defineMandatoryTransition(key, getINITIAL(), S_LI);

        if (useExtendedRules()) {
            // Open a "li" element if there is a new list
            defineTransition(key, S_UL, S_LI);
            defineTransition(key, S_OL, S_LI);
            defineTransition(key, S_DL, S_LI);
        }
    }

    // http://www.w3.org/TR/xhtml1/dtds.html#dtdentry_xhtml1-strict.dtd_ol
    // <!ELEMENT ul (li)+>
    protected void initOL() {
        initList(S_OL);
    }

    protected void initP() {
        IParseKey parent = getBlockParent();
        defineParent(S_P, parent);
        addInlineExtended(S_P);
    }

    protected void initPRE() {
        defineParent(S_PRE, getBlockParent());
    }

    protected void initSECTIONS() {
        IParseKey parent = S_DOCUMENT;
        for (int i = 0; i < S_SECTIONS.length; i++) {
            defineParent(S_SECTIONS[i], parent);
            defineParent(S_CONTENTS[i], S_SECTIONS[i]);
            defineParent(S_HEADS[i], S_SECTIONS[i]);

            // Section
            defineTransition(S_SECTIONS[i], getINITIAL(), getANY(), S_HEADS[i]);
            defineTransition(S_SECTIONS[i], S_HEADS[i], getANY(), S_CONTENTS[i]);
            defineTransition(
                S_SECTIONS[i],
                S_HEADS[i],
                getFINAL(),
                S_CONTENTS[i]);

            // Heads
            addInlineExtended(S_HEADS[i]);

            // Content
            addContentElements(S_CONTENTS[i]);
            defineTransition(S_CONTENTS[i], getANY(), S_SECTION1, getERROR());
            for (int j = 0; j <= i; j++) {
                defineTransition(S_CONTENTS[i], S_SECTIONS[j], getFINAL());
            }
            if (i < S_SECTIONS.length - 1) {
                defineTransition(
                    S_CONTENTS[i],
                    S_SECTIONS[i + 1],
                    S_SECTIONS[i + 1]);
            }

            parent = S_CONTENTS[i];
        }
    }

    @Override
    protected void initSTYLE() {
        defineParent(S_STYLE, S_P);
        defineSubstates(S_STYLE, S_PCDATA);
        defineSubstates(S_STYLE, S_A);
        defineSubstates(S_STYLE, S_BR);
    }

    protected void initTABLE() {
        defineParent(S_TABLE, getBlockParent());
        defineMandatoryTransition(S_TABLE, getINITIAL(), S_TR);
        defineTransition(S_TABLE, S_TR, S_TR, S_TR);
    }

    private void initTableCell(IParseKey key) {
        defineParent(key, S_TR);
        addBlockElements(key);
        if (useExtendedRules()) {
            // These additional transitions are defined to close an existing
            // table cell when an another "tr", "td" or "th" element is
            // occurred.
            IParseKey finalKey = getFINAL();
            defineTransition(key, S_TD, finalKey);
            defineTransition(key, S_TH, finalKey);
            defineTransition(key, S_TR, finalKey);
        }
    }

    protected void initTD() {
        initTableCell(S_TD);
    }

    protected void initTH() {
        initTableCell(S_TH);
    }

    protected void initTR() {
        defineParent(S_TR, S_TABLE);
        defineSubstates(S_TR, S_TD, S_TH);
        defineMandatoryTransition(S_TR, getINITIAL(), S_TD);
        if (useExtendedRules()) {
            // Exit from the state when a new row is reported.
            // (It should create a new row in the same table and not create
            // a new embedded table);
            defineTransition(S_TR, S_TR, getFINAL());
        }
    }

    // http://www.w3.org/TR/xhtml1/dtds.html#dtdentry_xhtml1-strict.dtd_ul
    // <!ELEMENT ul (li)+>
    protected void initUL() {
        initList(S_UL);
    }

    protected boolean useExtendedRules() {
        return true;
    }

}
